package dao

import (
	"errors"
	"fmt"
	"github.com/tianjigames/fairy/constants"
	"github.com/tianjigames/fairy/pojo"
	"github.com/topfreegames/pitaya"
	"github.com/topfreegames/pitaya/lock"
	"github.com/topfreegames/pitaya/logger"
	"math"
	"sync"
	"time"
)

var (
	GuidGeneratorDao *guidGeneratorDao
	guidGeneratorDaoOnce sync.Once
)


type guidGeneratorDao struct {
	BaseDao
	redisLocks map[int]*lock.RedisLock
}

func NewGuidGenertorDao() *guidGeneratorDao {
	guidGeneratorDaoOnce.Do(func() {
		length := constants.GuidTypeRedPacket - constants.GuidTypePlayer
		GuidGeneratorDao = &guidGeneratorDao{redisLocks: make(map[int]*lock.RedisLock,length)}


	})
	return GuidGeneratorDao
}

func (p *guidGeneratorDao) Init()  {
	for i := constants.GuidTypePlayer;i <= constants.GuidTypeRedPacket-constants.GuidTypePlayer;i++{
		GuidGeneratorDao.redisLocks[int(i)] = lock.NewRedisLock(fmt.Sprintf("guid_type_%d",i))
	}
}

func (g *guidGeneratorDao) GenGuid(typ int) (int64,error) {
	redisLock,ok := g.redisLocks[typ]
	if !ok {
		return 0,errors.New("typ is not existed")
	}

	result,err := redisLock.TryLock(func(args ...interface{}) (i interface{}, err error) {
		guiData,err := GuidDataDao.Get(&pojo.GuidData{Id: typ})
		if err != nil {
			logger.Log.Errorf("GenGuid failed,error:%s",err.Error())
			return 0,err
		}

		if guiData == nil {
			return 0,errors.New("type not existed")
		}

		guid,err := g.nextGuid(guiData.(*pojo.GuidData))
		if err != nil {
			logger.Log.Errorf("GenGuid failed,error:%s",err.Error())
			return 0,err
		}
		return guid,nil
	},3*time.Second)
	defer redisLock.UnLock()

	if err != nil {
		logger.Log.Errorf("GenGuid failed,error:%s",err.Error())
		return 0,err
	}

	return result.(int64),nil
}

func (g *guidGeneratorDao) nextGuid(guidData *pojo.GuidData) (int64,error) {
	worldId := pitaya.GetConfig().GetInt("pitaya.game.loginserver.worldid")
	perch := guidData.Perch
	low := guidData.Low + 1
	if low <0 || low > math.MaxInt32 {
		low = 0
		perch += 1
		guidData.Perch = perch
	}

	a := (int64(worldId)) << 48
	b := (int64(guidData.Id)) << 40
	c := (int64(perch)) << 32
	d := int64(low)
	guid := a|b|c|d
	guidData.Low = low
	err := GuidDataDao.Update(guidData,"id=?",guidData.GetGuid())
	if err != nil {
		logger.Log.Errorf("nextGuid failed,error:%s",err.Error())
		return 0,err
	}
	return guid,nil
}